package com.deneme;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
import com.google.appengine.api.images.Image;
import com.google.appengine.api.images.ImagesServiceFactory;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

import functions.images.AlgorithmsForCloudProject;
import functions.images.CommonHTML;
import functions.images.CommonHTML.PageName;
import functions.images.PuzzleDifficulty;

public class Puzzle extends HttpServlet {
    private static BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
    private static ArrayList<BlobKey> blobKeys;

    public void doGet(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
    	doPost(req, res);
    }
    
    public void doPost(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException {
    	try{
    	String key = req.getParameter("puzzle-key");
    	
    	// Get the saved puzzle to display
    	SavedPuzzle puzzle = SavedPuzzle.getPuzzleForKey(key);
    	
    	// If there is not puzzle, something went wrong.  Notify the user
    	if(puzzle == null){
    		req.setAttribute("errormsg", "Invalid Puzzle Identifier");
    		RequestDispatcher rd = req.getRequestDispatcher("/error");
    		rd.forward(req, res);
    		return;
    	}
    	
    	// Get the puzzle difficulty, or default to medium if there was an error
    	PuzzleDifficulty difficulty = puzzle.getDifficulty();
    	if (difficulty == null) {
    		difficulty = PuzzleDifficulty.medium;
    	}
    	
    	// Get the image associated with the puzzle
		BlobKey blobKey = puzzle.getImgKey();
		byte[] imgarr = blobstoreService.fetchData(blobKey, 0, blobstoreService.MAX_BLOB_FETCH_SIZE - 1);
		Image oldImage = ImagesServiceFactory.makeImage(imgarr);
		
		// Get the dimension for the puzzle, and based on the difficulty, get the number of shuffles
		int dimension = puzzle.getDimension();
		int shuffles = dimension*dimension;
		switch (difficulty) {
			default: // (if you didn't choose one, we'll "choose" one for you)
				shuffles *= dimension * dimension;
			case hard:
				shuffles *= dimension;
			case medium:
				shuffles *= dimension;
			case easy:
				shuffles *= dimension;
				break;
		}
		int[] shuffledArray;
		
		// Split the image into smaller images and get the blobkeys for each of those
		blobKeys = AlgorithmsForCloudProject.splitIntoArray(oldImage, dimension);
		
		// Get the saved shuffle array.  If there is none, generate one.
		shuffledArray = puzzle.getConfig();
		if(shuffledArray == null){
			shuffledArray = AlgorithmsForCloudProject.generateShuffledPuzzle_v2(dimension, shuffles);
		}
		
		// Get the user's data and mark the smaller images for deletion
		UserService userService = UserServiceFactory.getUserService();
		User user = userService.getCurrentUser();
		UserData data = UserData.getDataForUser(user);
		for(BlobKey bk : blobKeys) {
			data.addImageToDelete(bk);
		}
		
		// Create the html table that holds the puzzle tiles.  Also, associate each spot in the table with its appropriate image.
		StringBuilder sb_table = new StringBuilder();
		sb_table.append("<table border='0' cellpadding='0' cellspacing='0'>");
		int k = 0;
		int blank = dimension * dimension - 1;
		for(int i = 0; i < dimension; ++i) {
			sb_table.append("<tr>\n");
			for (int j = 0; j < dimension; ++j) {
				if(shuffledArray[k] == (dimension * dimension - 1)) {
					sb_table.append("	<td><a href='#' onclick='doclick(\"" + k + "\")' id='" + k + "'><img src='/images/empty_tile.jpg' height='" + oldImage.getHeight()/(double)dimension + "' width='" + oldImage.getWidth()/(double)dimension + "' /></a></td>\n");
					blank = k;
				}
				else {
					sb_table.append("	<td><a href='#' onclick='doclick(\"" + k + "\")' id='" + k + "'><img src='/serve?blob-key=" + blobKeys.get(shuffledArray[k]).getKeyString() + "' height='" + oldImage.getHeight()/(double)dimension + "' width='" + oldImage.getWidth()/(double)dimension + "' /></a></td>\n");
				}
				k++;
			}
			sb_table.append("</tr>\n");
		}
		sb_table.append("</table>");
		String imagesTable = sb_table.toString();
		
		// Store the initial shuffled array, and the complete array in strings so that we can let be visible to our javascript code.
		StringBuilder sb_shuffled = new StringBuilder();
		StringBuilder sb_complete = new StringBuilder();
		k = 0;
		for (int i = 0; i < dimension; ++i) {
			for (int j = 0; j < dimension; ++j) {
				if(i != dimension - 1 || j!= dimension -1) {
					sb_complete.append(k + ", ");
					sb_shuffled.append(shuffledArray[k++] + ", ");
				}
				else {
					sb_complete.append(k);
					sb_shuffled.append(shuffledArray[k++]);
				}
			}
		}
		
		String shuffledString = sb_shuffled.toString();
		String completeString = sb_complete.toString();
		
		String javascript = "var blank = " + blank + ";\n" + // stores which spot the blank is in
				"var n = " + dimension + ";\n" + // stores the dimensions of the puzzle
				"var config = [" + shuffledString + "];\n" + // stores where each piece is currently located
				"\n" +
				"var keys = [];\n" +
				// this listens for window event when key is pressed
				// will call dokeyclick to handle tile movement
				// overrides scrolling with arrow keys on this page
				"window.addEventListener(\"keydown\",\n" +
				"    function(e){\n" +
				"        keys[e.keyCode] = true;\n" +
				"        switch(e.keyCode){\n" +
				"            case 37: case 39: case 38:  case 40: // Arrow keys\n" +
				"            dokeyclick(e); e.preventDefault(); break;\n" +
				"            default: break; // do not block other keys\n" +
				"        }\n" +
				"    },\n" +
				"false);\n" +
				"window.addEventListener(\"keyup\",\n" +
				"    function(e){\n" +
				"        keys[e.keyCode] = false;\n" +
				"    },\n" +
				"false);\n" +
				"\n" +
				"function sendalert(msg) {\n" +
				"	alert(msg);\n" +
				"}\n" +
				"\n" +
				// this will update the locations of the tiles in the hidden form fields
				// that store them for saving later
				"function updatesavedata() {" +
				"	var savedconfig = document.getElementById(\"savedconfig\");\n" +
				"	var i = 0;" +
				"	savedconfig.value =\"\";\n" +
				"	for (i = 0; i < config.length; i = i + 1) {\n" +
				"		savedconfig.value = savedconfig.value + config[i] + \":\";\n" +
				"	}\n" +
				"}" +
				"\n" +
				// when a key is pressed this gets called to handle it
				"function dokeyclick(e) {\n" +
				"	var unicode = e.keyCode ? e.keyCode : e.charCode;\n" +
				"	var tile = parseInt(-1);\n" +
				// based on which key was pressed, figure out which tile needs to move
				"	switch(unicode) {\n" +
				"		case 37:\n" +
				"			tile = parseInt(blank) - 1;\n" +
				"			break;\n" +
				"		case 38:\n" +
				"			tile = parseInt(blank) - parseInt(n);\n" +
				"			break;\n" +
				"		case 39:\n" +
				"			tile = parseInt(blank) + 1;\n" +
				"			break;\n" +
				"		case 40:\n" +
				"			tile = parseInt(blank) + parseInt(n);\n" +
				"			break;\n" +
				"	}\n" +
				// validate that the move is valid (i.e. the tile to be moved is next to the blank)
				"	if(parseInt(tile) >= 0 && parseInt(tile) < (parseInt(n) * parseInt(n))) {\n" +
				"		doclick(tile.toString());\n" + // do the click
				"	}\n" +
				"}\n" +
				"\n" +
				// when a key is pressed or tile is clicked handle it
				"function doclick(i) {\n" +
				"	var j = blank;\n" +
				"	var intI = parseInt(i);\n" + // convert variables to integers 
				"	var intJ = parseInt(j);\n" + // for comparison
				"	if(intJ < intI) {\n" + // make sure j >= i
				"		var temp = intJ;\n" +
				"		intJ = intI;\n" +
				"		intI = temp;\n" +
				"	}\n" +
				// validate that the move is valid, ie that the two tiles are next to each other.
				"	if(((Math.floor(intI/n) == Math.floor(intJ/n)) && ((intJ-intI) == 1)) || ((intI%n == intJ%n) && ((intJ - intI) == n))) {\n" +
//				"		alert(\"adjacent\");\n" +
				"		swap(i, j);\n" +
				"	}\n" +
				"}\n" +
				"\n" +
				// swaps the position of two tiles
				"function swap(i, j) {\n" +
				// swap the inner html of the two tiles
				"	var pic1 = document.getElementById(i);\n" +
				"	var pic2 = document.getElementById(j);\n" +
				"	var temp = pic1.innerHTML;\n" +
				"	pic1.innerHTML = pic2.innerHTML;\n" +
				"	pic2.innerHTML = temp;\n" +
				// update the location of the blank
				"	if(i == blank) {\n" +
				"		blank = j;\n" +
				"	}\n" +
				"	else {\n" +
				"		blank = i;\n" +
				"	}\n" +
				// update the configuration array
				"	var temp2 = config[i];\n" +
				"	config[i] = config[j];\n" +
				"	config[j] = temp2;\n" +
				// if the blank is in the bottom right corner...
				"	if(parseInt(blank) == ((parseInt(n)*parseInt(n))-1)) {\n" +
					// check to see if the puzzle is solved.
				"		if (checkComplete()) {\n" +
						// if so, alert the user that they have won
				"			alert(\"Congrats!  You solved the puzzle!\");\n" +
				"		}\n" +
				"	}\n" +
				// update the configuration in the hidden field
				"	updatesavedata();\n" +
				"}\n" +
				"\n" +
				// check if two arrays are equal
				"function arrays_equal(a,b) {\n" +
				"	return !!a && !!b && !(a<b || b<a);\n" +
				"}\n" +
				"\n" +
				// check to see if the puzzle is complete
				"function checkComplete() {\n" +
				"	var complete = [" + completeString + "];\n" +
				"	return arrays_equal(complete,config);\n" +
				"}\n";
		// this code enables posting to the user's facebook wall
		String facebookStuff =
				// fb-root to be filled asynchronously. This includes the whole FB JavaScript API
				"<div id=\"fb-root\"></div>\n" +
				// Begining of the JavaScript that uses the Facebook API
				"<script type=\"text/javascript\">\n" +
				// Facebook login button. More like a place-holder
				"	var button;\n" +
				// UserInfo. Not absolutely needed, but a convenience at this point.
		        "   var userInfo;\n" +
				// AccessToken. This is being used on calls to the Graph API
		        "   var accessToken;\n" +
				// part of an old code at this point.
				"	var gotPhotos = false;\n" +
				// Facebook Asynchronous Initialization. Required before any other Facebook API call.
		        "   window.fbAsyncInit = function() {\n" +
						//Application ID is hardcoded
		        "       FB.init({ appId: '338030316264567', //change the appId to your appId\n" +
		        "           status: true,\n" + 
		        "           cookie: true,\n" +
		        "           xfbml: true,\n" +
		        "           oauth: true});\n" +
	
		        "      showLoader(true);\n" +
		        // Fills out the login/logout button, to be used if needed.       
		        "      function updateButton(response) {\n" +
		        "           button       =   document.getElementById('fb-auth');\n" +
		        "           userInfo     =   document.getElementById('user-info');\n" +
		                    
		        "           if (response.authResponse) {\n" +
		        "                //user is already logged in and connected\n" +
		        "                FB.api('/me', function(info) {\n" +
		        "                   login(response, info);\n" +
		        "                });\n" +
		        "                \n" +
		        "               button.onclick = function() {\n" +
		        "                   FB.logout(function(response) {\n" +
		        "                       logout(response);\n" +
		        "                    });\n" +
		        "                };\n" +
		        "            } else {\n" +
		        "                //user is not connected to your app or logged out\n" +
		        "                button.innerHTML = 'Login';\n" +
		        "                button.onclick = function() {\n" +
		        "                    showLoader(true);\n" +
		        "                    FB.login(function(response) {\n" +
		        "                        if (response.authResponse) {\n" +
		        "                            FB.api('/me', function(info) {\n" +
		        "                                login(response, info);\n" +
		        "                            });	  \n" + 
		        "                        } else {\n" +
		        "                            //user cancelled login or did not grant authorization\n" +
		        "                            showLoader(false);\n" +
		        "                        }\n" +
		        "                    }, {scope:'user_photos,status_update,publish_stream,user_about_me'});\n" +  	
		        "                }\n" +
		        "            }\n" +
		        "        }\n" +
		                
		        "       // run once with current status and whenever the status changes\n" +
		        "       FB.getLoginStatus(updateButton);\n" +
		        "       FB.Event.subscribe('auth.statusChange', updateButton);\n" +	
		        "    };\n" +
		        "    (function() {\n" +
		        "       var e = document.createElement('script'); e.async = true;\n" +
		        "       e.src = document.location.protocol \n" +
		        "            + '//connect.facebook.net/en_US/all.js';\n" +
		        "       document.getElementById('fb-root').appendChild(e);\n" +
		        "    }());\n" +
		        "\n" +
				// Share function, to be called when Facebook API is initialized AND user clicks SHARE button.
				// This function calls the Share function with the URL of the current puzzle
				"	function share(){\n" +
				"		var share = {\n" +
				"	   		method: 'feed'," +
				"			name: 'Cloud Shuffle Puzzle'," +
				"			link: 'http://cloud-n-puzzle-test-1.appspot.com/puzzle?puzzle-key=" + key + "'," +
				"			picture: 'http://cloud-n-puzzle-test-1.appspot.com/serve?blob-key=" + blobKey.getKeyString() + "'," +
				"			caption: 'Custom Image Puzzles'," +
				"			description: 'Try out this puzzle!'" +
				"		};\n" +
				"		FB.ui(share, function(response) {\n" +
				"	});\n" +
				"}\n" +
				"</script>\n";
		
		/* this form contains hidden fields with all information needed to save the puzzle 
		 * this is used when the user clicks the "save" button */
		String saveForm = "<form action='/savepuzzle' method='post'>\n" +
				"<input type='hidden' name='puzzlekey' id='savedpuzzlekey' value='" + key + "' />\n" +
				"<input type='hidden' name='config' id='savedconfig' />\n" +
				"<input type='hidden' name='dimension' id='saveddimension' value='" + dimension + "' />" +
				"<input type='hidden' name='blobkey' id='savedblobkey' value='" + blobKey.getKeyString() + "' />\n" +
				"<input type='hidden' name='difficulty' id='saveddifficulty' value='" + difficulty.toString() + "' />" +
				"<input type='hidden' name='savetouser' id='savetouser' value='true' />\n" +
				"<input type='submit' class='fun_button' name='save' value='Save Puzzle' />" +
				"</form>\n" +
				// Share button to make a Facebook Share call
				"<button class='fun_button' onclick='share()'>Share on Facebook</button>";
		
		/* generate the output for the puzzle page */
		res.setContentType("text/html");
		PrintWriter out = res.getWriter();
		out.print("<html xmlns:fb=\"https://www.facebook.com/2008/fbml\">\n" +
				"<head>\n" +
				"	<link rel=\"stylesheet\" href=\"/styles.css\" type=\"text/css\" />\n" +
				"	<title>Solve this puzzle!</title>\n" +
				"	<script type=\"text/javascript\">\n" +
				javascript + // this includes the javascript created above
				"	</script>\n" +
				"</head>\n" +
				"<body onload='updatesavedata()' height='100%'>\n" +
				facebookStuff+ // this includes facebook functionality
				"	<div class='content'>" +
				CommonHTML.getNavBar(PageName.none) + // this includes the menu bar at the top of the page
				"	<div id='img' class='puzzle_table'>\n" +
				imagesTable + // this includes the puzzle itself
				"	</div>\n" +
				"	<div id='saveform' class='save_form'>\n" +
				saveForm + // this includes the hidden form with the save button 
				"	</div>\n" +
				
				//"<button id=\"fb-auth\">Login</button>\n" +
				//"<a href=\"#\" onclick=\"share(); return false;\">Share With Your Friends</a>\n"+
				
				"	</div><!-- content -->\n" +
				"</body>\n" +
				"</html>\n");
    	}catch(Exception e){
    		/* if there is an error, forward the user to custom error page */
    		req.setAttribute("errormsg", "An error occured");
    		RequestDispatcher rd = req.getRequestDispatcher("/error");
    		rd.forward(req, res);
    		return;
    	}
        
    }
}
